﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Migrator.Entities;
using Migrator.Services.DHCP.Packets;
using System.Collections;
using Migrator.Util;
using Migrator.Packets;

namespace Migrator.Services.DHCP
{
    /// <summary>
    /// Simple DHCP server
    /// </summary>
    public class DHCPServer : IService
    {
        public DHCPServer(Node client)
        {
            this.Client = client;
        }

        public void Update()
        { 
        }

        public bool Filter(IPPacket packet)
        {
            // DHCP server is only interested in DHCP discover and DHCP request packets
            return (packet is DHCPDiscoverPacket || packet is DHCPRequestPacket);
        }

        public void Handle(IPPacket packet)
        {
            if (packet is DHCPDiscoverPacket)
                this.Handle((DHCPDiscoverPacket)packet);

            else if (packet is DHCPRequestPacket)
                this.Handle((DHCPRequestPacket)packet);
        }

        /// <summary>
        /// DHCP server configuration
        /// </summary>
        /// <param name="configuration">
        /// [0] pool start
        /// [1] pool end
        /// </param>
        /// <returns>Itself</returns>
        public IService Configure(params object[] configuration)
        {
            byte suffix = this.Client.IPAddress.GetAddressBytes().Last();

            int poolMin = (int) configuration[0];
            int poolMax = (int) configuration[1];
            if (suffix >= poolMin && suffix <= poolMax)
            {
                if (suffix - poolMin > poolMax - suffix)
                    poolMax = suffix - 1;
                else
                    poolMin = suffix + 1;
            }

            if (poolMax < poolMin)
                throw new ApplicationException("DHCPServer::InitializePool: " + poolMax + " is less than " + poolMin);

            this.poolMin = poolMin;
            this.poolMax = poolMax;
            this.reserved = new Node[poolMax - poolMin + 1];

            this.Enabled = true;
            return this;
        }

        public bool Enabled
        {
            get;
            set;
        }

        public Node Client
        {
            get;
            protected set;
        }

        #region Internal methods
        private void Handle(DHCPDiscoverPacket packet)
        {
            byte suffix = GetFirstAvailable();
            if (suffix != 0)
            {
                this.Reserve(suffix, packet.Source);
                IPAddress reservedAddress = new IPAddress(this.Client.IPAddress.GetAddressBytes().Take(3).Concat(new[] { suffix }).ToArray());
                this.Client.SendLinkLayer(new DHCPOfferPacket(this.Client, reservedAddress), packet.Source.MACAddress);
            }
        }

        private void Handle(DHCPRequestPacket packet)
        {
            // If the node accepted my offer, send an ACK
            if (packet.AcceptedServerIP == this.Client.IPAddress)
            {
                // If I have a reservation for this node
                byte suffix = packet.AcceptedIP.GetAddressBytes().Last();
                if (this.reserved[suffix - poolMin] != null)
                    this.Client.SendLinkLayer(new DHCPAckPacket(this.Client, packet.AcceptedIP), packet.Source.MACAddress);
            }
            else
            {
                // Otherwise, free the reservation
                FreeReservation(packet.Source);
            }
        }

        private byte GetFirstAvailable()
        {
            for (int i = poolMin; i <= poolMax; i++)
            {
                if (reserved[i - poolMin] == null)
                {
                    return (byte)i;
                }
            }

            return 0;
        }

        private void Reserve(int suffix, Node node)
        {
            reserved[suffix - poolMin] = node;
        }

        private void FreeReservation(Node node)
        {
            for (int i = 0; i < reserved.Length; i++)
            {
                if (reserved[i] == node)
                    reserved[i] = null;
            }
        } 
        #endregion

        private int poolMin;
        private int poolMax;
        private Node[] reserved;
    }
}
